package fr.insta.reverse.uml.java.projectParser;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.bcel.classfile.DescendingVisitor;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;

public class ServiceUML
{
	List<JavaClass> javaClasses;

	// pour garder en memoire les packages de la solution sans boucles toutes les classes
	ArrayList<String[]> mappedClasses = new ArrayList<String[]>();

	// les adresses de toutes les classes (pour les instancier si besoin)
	ArrayList<String> fullClassNames;

	// liste des relations (pour ne pas traiter a nouveua une relation dans l'autre sense si elle est bidirectionelle)
	ArrayList<String> dependencies;

	// classes  de repere pour le parsing
	JavaClass classeDeBase;
	JavaClass confrontationClass = null;
	boolean isAssociation;

	// classe field, pour avoir un repere sur le field sur le quel on est 
	Field fieldDeBase;

	// arraylist de string mis en place pour respecter l'ordre d'envoi des donn�es.
	ArrayList<String> classes = new ArrayList<String>();
	ArrayList<String> relations = new ArrayList<String>();

	public ServiceUML(List<JavaClass> c)
	{
		this.javaClasses = c;
	}

	public ArrayList<String> generate()
	{
		// je genere les noms des classes dans mapped classes
		// cette operation permet de boucler une proprit� de type objet de notre classe
		// et la confronter avec les classes de notre projet.
		// car il est inutile de rajouter dans notre diagramme les classes des packages "java.util.*" par exemple
		loadFilesNames();

		for (Iterator<JavaClass> iterator = javaClasses.iterator(); iterator.hasNext();)
		{
			classeDeBase = (JavaClass) iterator.next();

			//cette methode pharse la classe et renseigne dans les stringbuffer
			// la classe pour la dessiner
			// ces methodes
			// ces proprietes
			// son interface
			// ses implementations
			generationClasseUML(classeDeBase);
		}

		for (Iterator<JavaClass> iterator = javaClasses.iterator(); iterator.hasNext();)
		{
			classeDeBase = (JavaClass) iterator.next();
			// ajout des associations
			isAssociation = false;
			generateAssociation();

			// si ce n'est pas une association, je controle pour les aggregations et les compositions
			if (!isAssociation)
			{
				generateAgregationEtComposition();
			}

		}

		// pour affichage ***** TEST

		for (int i = 0; i < classes.size(); i++)
		{
			System.out.println(classes.get(i).toString());
		}
		for (int i = 0; i < relations.size(); i++)
		{
			System.out.println(relations.get(i).toString());
		}

		// FIN TEST

		// je fusionne les buffestring pour renvoyer une seule liste
		classes.addAll(relations);

		return classes;
	}

	private void generationClasseUML(JavaClass jc)
	{
		//generation de la classe de base UML

		ArrayList<String> buff = new ArrayList<String>();
		StringBuffer sb = new StringBuffer();
		// on commence par créer la classe avec les proprietes et les methodes

		if (jc.getFields().length == 0 && jc.getMethods().length == 0)
		{
			buff.add("[" + jc.getClassName() + "]");
		}
		else
		{
			sb.append("[" + jc.getClassName() + "|");
			Field[] att = jc.getFields();
			Method[] met = jc.getMethods();
			//jc.get

			//properties
			for (int i = 0; i < att.length; i++)
			{
				if (i > 0)
				{
					sb.append(";");
				}

				// recuperation premier caractere
				String init = att[i].getSignature().substring(0, 1);

				if (init.equals("L"))
				{
					int ref = att[i].getSignature().lastIndexOf("/");

					if (ref == -1)
					{
						ref = 0;
					}

					sb.append(att[i].getSignature().substring(ref + 1).replace(";", "") + " ");
				}
				else if (init.equals("I"))
				{
					sb.append("Int ");
				}
				else if (init.equals("B"))
				{
					sb.append("Byte ");
				}
				else if (init.equals("C"))
				{
					sb.append("Char ");
				}
				else if (init.equals("F"))
				{
					sb.append("Float ");
				}
				else if (init.equals("J"))
				{
					sb.append("Long ");
				}
				else if (init.equals("S"))
				{
					sb.append("Short ");
				}
				else if (init.equals("Z"))
				{
					sb.append("boolean ");
				}
				else if (init.equals("["))
				{
					sb.append("Array() ");
				}

				sb.append(att[i].getName());
			}
			sb.append("|");

			//methods
			for (int i = 0; i < jc.getMethods().length; i++)
			{
				if (!met[i].getName().equals("<init>"))
				{
					sb.append(met[i].getName() + "();");
				}
			}
			sb.append("]");
			buff.add(sb.toString());
			sb = new StringBuffer();
		}

		//example exit :  [User|+Forename+;Surname;+HashedPassword;-Salt|+Login();+Logout()]

		//ajout de l'heritage
		//example [Wages]^-[Salaried]
		if (!jc.getSuperclassName().equals("java.lang.Object"))
		{
			buff.add("[" + jc.getSuperclassName() + "]^-extends[" + jc.getClassName() + "]");
			mappedClasses.add(new String[] { jc.getSuperclassName(), jc.getClassName() });
		}

		// ajout des interfaces
		//example  [<<ITask>>]^-.-[NightlyBillingTask]
		if (!(jc.getInterfaceNames().length == 0))
		{
			String[] interf = jc.getInterfaceNames();
			for (int i = 0; i < interf.length; i++)
			{
				String cache = interf[i];
				buff.add("[" + cache + "]^-.-implements[" + jc.getClassName() + "]");
				mappedClasses.add(new String[] { cache, jc.getClassName() });
			}
		}

		for (int i = 0; i < buff.size(); i++)
		{
			classes.add(buff.get(i).toString());
		}

	}

	private void generateAgregationEtComposition()
	{
		// on controle donc si c'est une agregation ou une composition

		// je controle quelles sont les variables de la classe
		DependencyEmitter visitor = new DependencyEmitter(classeDeBase);
		DescendingVisitor classWalker = new DescendingVisitor(classeDeBase, visitor);
		classWalker.visit();
		dependencies = visitor.getListDepencencies();

		// maintenant j'ai les dependences de la classe :
		// ces variables sont INSTANCIE (new) ->DANS<- la classe
		// si nous trouvons donc une correspondance c'est une COMPOSITION
		// sinon une AGGREGATION
		String compare = fieldDeBase.getSignature().substring(1).replaceAll(";", "").replaceAll("/", ".");
		boolean isComposition = false;
		for (int k = 0; k < dependencies.size(); k++)
		{
			if ((dependencies.get(k).replaceAll("/", ".")).equals(compare))
			{
				isComposition = true;
			}
		}

		if (isComposition && !confrontationClass.getClassName().equals(classeDeBase.getClassName()))
		{
			// c'est une composition
			//je controle qu'elle n'existe déjà
			boolean exist2 = false;
			for (int k = 0; k < mappedClasses.size(); k++)
			{
				String[] e = mappedClasses.get(k);
				//si le lien n'existe pas je vais donner l'ordre de le creer
				if ((e[1].equals(classeDeBase.getClassName()) && e[0].equals(confrontationClass.getClassName())) || (e[1].equals(confrontationClass.getClassName()) && e[0].equals(classeDeBase.getClassName())))
				{
					exist2 = true;
				}
			}
			if (!exist2 || mappedClasses.size() == 0)
			{
				//exemple [Company]++-1>[Location]

				if (classeDeBase.getClassName().contains("$") || confrontationClass.getClassName().contains("$"))
				{
					relations.add("[" + confrontationClass.getClassName() + "]++-[" + classeDeBase.getClassName() + "]");
					mappedClasses.add(new String[] { confrontationClass.getClassName(), classeDeBase.getClassName() });
				}
				else
				{
					relations.add("[" + classeDeBase.getClassName() + "]++-[" + confrontationClass.getClassName() + "]");
					mappedClasses.add(new String[] { confrontationClass.getClassName(), classeDeBase.getClassName() });
				}
			}
		}
		else if (!confrontationClass.getClassName().equals(classeDeBase.getClassName()))
		{
			// c'est une aggregation! 
			boolean exist3 = false;
			for (int k = 0; k < mappedClasses.size(); k++)
			{
				String[] e = mappedClasses.get(k);
				//si le lien n'existe pas je vais donner l'ordre de le creer
				if ((e[1].equals(classeDeBase.getClassName()) && e[0].equals(confrontationClass.getClassName())) || (e[1].equals(confrontationClass.getClassName()) && e[0].equals(classeDeBase.getClassName())))
				{
					exist3 = true;
				}
			}
			if (!exist3 || mappedClasses.size() == 0)
			{
				//exemple [Company]<>-1>[Location]
				if (classeDeBase.getClassName().contains("$") || confrontationClass.getClassName().contains("$"))
				{
					relations.add("[" + confrontationClass.getClassName() + "]<>-[" + classeDeBase.getClassName() + "]");
					mappedClasses.add(new String[] { confrontationClass.getClassName(), classeDeBase.getClassName() });
				}
				else
				{
					relations.add("[" + classeDeBase.getClassName() + "]<>-[" + confrontationClass.getClassName() + "]");
					mappedClasses.add(new String[] { confrontationClass.getClassName(), classeDeBase.getClassName() });
				}
			}

		}

	}

	private void loadFilesNames()
	{
		//procedure pour recuperer les noms des packages
		fullClassNames = new ArrayList<String>();
		for (Iterator<JavaClass> iterator = javaClasses.iterator(); iterator.hasNext();)
		{
			JavaClass cache = (JavaClass) iterator.next();
			fullClassNames.add(cache.getClassName());
		}
	}

	private JavaClass getJavaClassByName(String name)
	{

		JavaClass found = null;
		if (!javaClasses.isEmpty())
		{
			for (Iterator<JavaClass> iterator = javaClasses.iterator(); iterator.hasNext();)
			{
				JavaClass cache = (JavaClass) iterator.next();
				if (cache.getClassName().equals(name))
				{
					found = cache;
				}

			}
		}
		return found;
	}

	private void generateAssociation()
	{
		//je recupere les attribust un a un de ma classe de BASE
		//mais avant je controle si il en a 
		if (classeDeBase.getFields().length == 0)
		{
			isAssociation = true;
		}
		for (int i = 0; i < classeDeBase.getFields().length; i++)
		{
			fieldDeBase = (classeDeBase.getFields())[i];
			String init = classeDeBase.getFields()[i].getSignature().substring(0, 1);
			//si l'attribut est de type object et pas un primitif 
			if (!(init.equals("I") || init.equals("B") || init.equals("C") || init.equals("D") || init.equals("F") || init.equals("J") || init.equals("S") || init.equals("Z") || init.equals("[")))
			{

				boolean controlClassExist = false;
				String confront = classeDeBase.getFields()[i].getSignature().substring(1).replaceAll(";", "").replaceAll("/", ".");
				// je controle si le type de variable correspond a une classe du projet envoyé
				for (Iterator<String> iterator2 = fullClassNames.iterator(); iterator2.hasNext();)
				{
					String cacheControl = (String) iterator2.next();
					if (cacheControl.equals(confront))
					{
						//cette proprieté est à tratiter

						controlClassExist = true;
						confrontationClass = getJavaClassByName(cacheControl);

					}
				}

				// si le type de la proprieté est donc une autre classe du projet, je dois créer le lien
				if (controlClassExist)
				{

					//je controle dans la classe du type de propriete si nous avons une reference a notre classe (de base)
					//si oui c'est une relation bidirectionelle -> ASSOCIATION!

					//Field[] atts = classeDeBase.getFields();
					isAssociation = false;

					// si c'est la meme classe je ne fait rien
					if (!confrontationClass.getClassName().equals(classeDeBase.getClassName()))
					{

						String signature2 = classeDeBase.getClassName();

						for (int j = 0; j < confrontationClass.getFields().length; j++)
						{
							String signature = confrontationClass.getFields()[j].getSignature().substring(1).replaceAll(";", "").replaceAll("/", ".");;
							if (signature.equals(signature2))
							{
								//c'est une association!
								//example [Customer]-[Billing Address]
								// je controle que cette relation n'existe pas d�j�
								boolean exist = false;
								for (int k = 0; k < mappedClasses.size(); k++)
								{
									String[] e = mappedClasses.get(k);
									//si le lien n'existe pas je vais donner l'ordre de le creer
									if ((e[1].equals(classeDeBase.getClassName()) && e[0].equals(confrontationClass.getClassName())) || (e[1].equals(confrontationClass.getClassName()) && e[0].equals(classeDeBase.getClassName())))
									{
										exist = true;
									}
								}
								if (!exist || mappedClasses.size() == 0)
								{
									relations.add(" [" + confrontationClass.getClassName() + "]-[" + classeDeBase.getClassName() + "]");
									mappedClasses.add(new String[] { confrontationClass.getClassName(), classeDeBase.getClassName() });
									isAssociation = true;
								}

							}

						}
						if (!isAssociation)
						{
							generateAgregationEtComposition();
						}

					}
					else
					{
						//si nous analysont la meme classe, on n'affiche pas de relation
						isAssociation = true;//on triche ;)

					}

				}
				else
				{
					//ce n'est pas une classe de notre projet, on n'affiche pas la relation
					isAssociation = true;//on triche ;)

				}

			}
		}
		if (confrontationClass == null)
		{
			isAssociation = true;
		}

	}
}
